﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Hidden_Markov_Model
{
	class StateElement
	{
		public enum State { Begin = 0, Raining = 1, Sunny = 2, Cloudy = 3, End = 4 };
		public enum Emission { Shopping = 0, Sleeping = 1, Walking = 2 };

		//private State _state { get; set; }
		private Emission _emission { get; set; }
		private int _emissionInt { get; set; }

		/*
		/// <summary>
		/// Holds the transition probabilites for the given entry to the begin, raining, sunny, cloudy, and end states respectively
		/// </summary>
		public static Dictionary<State, double[]> transitionTable = new Dictionary<State,double[]>()
		{
			{State.Begin, new double[5] {0.0, 0.33, 0.33, 0.33, 0}},
			{State.Raining, new double[5] {0.0, .75, .04, .2, .01}},
			{State.Sunny, new double[5] {0.0, .04, .85, .1, .01}},
			{State.Cloudy, new double[5] {0.0, .3, .19, .5, .01}}
		};

		/// <summary>
		/// Holds the emission probabilities for the given entry for the Shopping, Sleeping, and Walking emissions respectively
		/// </summary>
		public Dictionary<State, double[]> emissionTable = new Dictionary<State,double[]>()
		{
			{State.Raining, new double[3] {0.3, 0.6, 0.1}},
			{State.Sunny, new double[3] {0.35, 0.05, 0.6}},
			{State.Cloudy, new double[3] {0.75, 0.15, 0.1}}
		};
		 */

		/// <summary>
		/// Holds the transition probabilites for the given entry to the begin, raining, sunny, cloudy, and end states respectively
		/// </summary>
		public static double[,] transitionTable = new double[,]
		{

			{0.0, 0.33, 0.33, 0.33, 0},	//begin
			{0.0, .75, .04, .2, .01},	//raining
			{0.0, .04, .85, .1, .01},	//sunny
			{0.0, .3, .19, .5, .01},	//cloudy
			{0.0, 0.0, 0.0, 0.0, 0.0}	//end
		};

		/// <summary>
		/// Holds the emission probabilities for the given entry for the Shopping, Sleeping, and Walking emissions respectively
		/// </summary>
		public static double[,] emissionTable = new double[,]
		{ 
			//shopping, sleeping, walking
			{0.0, 0.0, 0.0},		//begin
			{0.3, 0.35, 0.75},		//Raining
			{0.6, 0.05, 0.15},		//Sunny
			{0.1, 0.6, 0.1},		//Cloudy
			{0.0, 0.0, 0.0}			//end
			
		};
		
		public StateElement()
		{

			//pick a random emission
			Random rng = new Random();
			int pickEmission = rng.Next(3);

			switch (pickEmission)
			{
				case 0:
					this._emission = Emission.Shopping;
					this._emissionInt = 0;
					break;
				case 1:
					this._emission = Emission.Sleeping;
					this._emissionInt = 1;
					break;
				default:
					this._emission = Emission.Walking;
					this._emissionInt = 2;
					break;
			}

		}


		/// <summary>
		/// An implementation of the viterbi algorithm. http://statarb.org/pdf/HMM/hmm.pdf
		/// This feels fucked up... Not sure how but something ain't right
		/// </summary>
		/// <param name="obs"></param>
		/// <returns></returns>
		public static List<State> Viterbi(int[] obs)
		{
			List<State> result = new List<State>();

			double[,] pathProbabilityMatrix = new double[5, obs.Length+1];
			double[,] backPointers = new double[5, obs.Length+1];
			pathProbabilityMatrix[0, 0] = 1;

			for (int t = 0; t < obs.Length; t++)
			{
				for (int s = 0; s < Enum.GetNames(typeof(State)).Length; s++)//s is states
				{
					//for each state s could transition to
					for (int i = 0; i < transitionTable.GetLength(0); i++)
					{
						double newScore = pathProbabilityMatrix[s, t] * transitionTable[s, i] * emissionTable[i,obs[t]];
						if (newScore > pathProbabilityMatrix[i, t + 1])
						{
							pathProbabilityMatrix[i, t + 1] = newScore;
							backPointers[i, t + 1] = s;
						}
					}
				}
			}

			result.Add(State.Begin);
			double maxProb;
			int maxProbIndex;

			//Now go through the path probability matrix and reconstruct the state at each stage in the sequence
			for (int j = 1; j <= obs.Length; j++)
			{
				maxProb = 0;
				maxProbIndex = 0;
				for (int i = 0; i < transitionTable.GetLength(0); i++)
				{
					if (pathProbabilityMatrix[i, j] > maxProb)
					{
						maxProb = pathProbabilityMatrix[i, j];
						maxProbIndex = i;
					}
				}
				result.Add((State)maxProbIndex);

			}

			return result;
		}

		/// <summary>
		/// Turns a string into an array of numbers representing events
		/// </summary>
		/// <param name="p"></param>
		/// <returns></returns>
		public static int[] StringToEvents(string p)
		{
			int[] result = new int[p.Length];

			for(int i = 0; i < p.Length; i++)
			{
				if (p[i] == 's' || p[i] == 'S')
					result[i] = 0;
				else if (p[i] == 'z' || p[i] == 'Z')
					result[i] = 1;
				else if (p[i] == 'w' || p[i] == 'W')
					result[i] = 2;
				else
					throw new Exception("Unknown event encountered. Possible events are s, S, z, Z, w, W");
			}

			return result;
		}
	}
}
